use async_std::io::{Read, Write};     // 引入异步读写 trait，方便 Mock 实现
use async_std::test;                  // `#[async_std::test]` 宏，用于异步测试
use futures::task::Context;
use std::cmp::min;
use std::pin::Pin;
use std::task::Poll;
use webserver::handle_connection;     // 被测的目标函数

/// 一个模拟的 TcpStream，以便在单元测试中替代真实网络连接。
/// - `read_data`：测试时预先灌入的请求数据，供 `poll_read` 读取；
/// - `write_data`：`handle_connection` 写出的响应会被收集到此处，供断言使用。
struct MockTcpStream {
    read_data: Vec<u8>,
    write_data: Vec<u8>,
}

impl MockTcpStream {
    /// 构造函数：只需提供下一次读取时要返回的数据。
    fn new(read_data: Vec<u8>) -> Self {
        Self {
            read_data,
            write_data: Vec::new(),
        }
    }
}

/// 模拟 `TcpStream` 的读取逻辑。
/// `Read` trait 由 async-std 定义，底层依赖 `poll_read`。
impl Read for MockTcpStream {
    fn poll_read(
        mut self: Pin<&mut Self>,
        _cx: &mut Context<'_>,      // 当前任务上下文（此处无须使用）
        buf: &mut [u8],             // 目标缓冲区，写入读取到的数据
    ) -> Poll<std::io::Result<usize>> {
        let this = self.as_mut().get_mut();

        // 如果没有剩余数据了，返回 0，告知上层读已结束。
        if this.read_data.is_empty() {
            return Poll::Ready(Ok(0));
        }

        // 取缓冲区容量与剩余数据长度的最小值，避免越界。
        let size = min(buf.len(), this.read_data.len());

        // 把模拟请求拷贝到目标缓冲区，模拟真实 socket 的读取效果。
        buf[..size].copy_from_slice(&this.read_data[..size]);

        // 移除已经“读取”过的数据，下一次调用就能返回后续片段。
        this.read_data.drain(..size);

        // 返回 `Poll::Ready` 表示立即完成，并返回读取到的字节数。
        Poll::Ready(Ok(size))
    }
}

/// 模拟 `TcpStream` 的写入逻辑。
/// `Write` trait 要实现 `poll_write`、`poll_flush` 和 `poll_close`。
impl Write for MockTcpStream {
    fn poll_write(
        mut self: Pin<&mut Self>,
        _cx: &mut Context<'_>,
        buf: &[u8],                  // 需要写入的数据
    ) -> Poll<std::io::Result<usize>> {
        let this = self.as_mut().get_mut();

        // 收集 `handle_connection` 写出的响应，供断言使用。
        this.write_data.extend_from_slice(buf);

        // 直接报告“写入成功”，并返回写入的字节数。
        Poll::Ready(Ok(buf.len()))
    }

    fn poll_flush(
        self: Pin<&mut Self>,
        _cx: &mut Context<'_>,
    ) -> Poll<std::io::Result<()>> {
        // 模拟流不需要额外 flush，所以直接返回准备就绪。
        Poll::Ready(Ok(()))
    }

    fn poll_close(
        self: Pin<&mut Self>,
        _cx: &mut Context<'_>,
    ) -> Poll<std::io::Result<()>> {
        // 模拟流也不需要特殊关闭动作，直接返回就绪。
        Poll::Ready(Ok(()))
    }
}

// 标记类型可以安全地在内存中移动，符合 `Unpin` 要求。
impl Unpin for MockTcpStream {}

/// 集成测试：确保 `handle_connection` 在收到 `GET /` 请求时写出正确响应。
#[test]
async fn test_handle_connection_serves_root() {
    // 构造一个 HTTP GET / 请求（不含完整 header，但足够触发分支）。
    let request = b"GET / HTTP/1.1\r\n";

    // `handle_connection` 会一次性读取 1024 字节，因此准备同样大小的缓冲区。
    let mut read_buf = vec![0u8; 1024];
    read_buf[..request.len()].copy_from_slice(request);

    // 使用预置请求数据实例化 MockTcpStream，初始时 `write_data` 为空。
    let mut mock = MockTcpStream::new(read_buf);

    // 调用被测函数。由于函数签名接受泛型流，这里直接传 &mut MockTcpStream。
    handle_connection(&mut mock).await;

    // 读取 hello.html 的内容，构造预期的响应文本。
    let expected_body =
        std::fs::read_to_string("hello.html").expect("hello.html should exist during test");
    let expected_response = format!("HTTP/1.1 200 OK\r\n\r\n{expected_body}");

    // 断言写出的数据以预期的响应开头（允许额外空白或后续字节）。
    assert!(
        mock.write_data.starts_with(expected_response.as_bytes()),
        "response mismatch:\nexpected prefix:\n{expected_response}\nactual:\n{}",
        String::from_utf8_lossy(&mock.write_data)
    );
}